﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;

using Acceron.University.DBAccess;

namespace GA_Scheduler
{
    public class Scheduler
    {
        // Number of crossover points of parent's class tables
        int _numberOfCrossoverPoints;

        // Number of classes that is moved randomly by single mutation operation
        int _mutationSize;

        // Probability that crossover will occure
        int _crossoverProbability;

        // Probability that mutation will occure
        int _mutationProbability;

        // Fitness value of chromosome
        float _fitness;

        // Flags of class requiroments satisfaction
        bool[] _criteria;

        // Time-space slots, one entry represent one hour in one classroom
        List<Class>[] _slots;

        // Class table for chromosome
        // Used to determine first time-space slot used by class
        public Dictionary<Class, int> _classes = new Dictionary<Class,int>();

        public const int DAYS_NUM = 5;
        public const int DAY_HOURS = 12;

        public Scheduler(int numberOfCrossoverPoints, int mutationSize,
                   int crossoverProbability, int mutationProbability)
        {
            _mutationSize = mutationSize;
            
            _numberOfCrossoverPoints = numberOfCrossoverPoints;
            _crossoverProbability = crossoverProbability;
            _mutationProbability = mutationProbability;
            _fitness = 0;

            // reserve space for time-space slots in chromosomes code
           _slots = new List<Class>[DAYS_NUM * DAY_HOURS * Configuration.Instance.NumberOfRooms];
            
            for (int i = 0; i < _slots.Length; i++)
                _slots[i] = new List<Class>();
          
        }

        public Scheduler(Scheduler c, bool setupOnly)
        {
            if (!setupOnly)
            {
                // copy code
                _slots = c._slots;
                _classes = c._classes;

                // copy flags of class requirements
                _criteria = c._criteria;

                // copy fitness
                _fitness = c._fitness;
            }
            else
            {
                _slots = new List<Class>[DAYS_NUM * DAY_HOURS * Configuration.Instance.NumberOfRooms];
                for (int i = 0; i < _slots.Length; i++)
                    _slots[i] = new List<Class>();

                _criteria = new bool[Configuration.Instance.NumberOfClasses * 5];
                // reserve space for time-space slots in chromosomes code
                //Array.Resize<List<Class>>(ref _slots, DAYS_NUM * DAY_HOURS * Configuration.Instance.NumberOfRooms);

                // reserve space for flags of class requirements
                // Array.Resize(ref _criteria, Configuration.Instance.NumberOfClasses * 5);
            }

            // copy parameters
            _numberOfCrossoverPoints = c._numberOfCrossoverPoints;
            _mutationSize = c._mutationSize;
            _crossoverProbability = c._crossoverProbability;
            _mutationProbability = c._mutationProbability;
        }

        // Makes copy ot chromosome
        public Scheduler MakeCopy(bool setupOnly)
        {
            // make object by calling copy constructor and return smart pointer to new object
            return new Scheduler(this, setupOnly);
        }

        // Makes new chromosome with same setup but with randomly chosen code
        public Scheduler MakeNewFromPrototype()
        {
            // number of time-space slots
            int size = (int)_slots.Length;

            // make new chromosome, copy chromosome setup
            Scheduler newChromosome = new Scheduler(this, true);

            // place classes at random position
            //List<Class> classes = Configuration.Instance.Classes;
            List<Class> classes = Configuration.Instance.Classes.ToList();
            //Random rnd = new Random();
            
            //for( list<Class>::const_iterator it = c.begin(); it != c.end(); it++ )
            foreach (var it in classes)
            {
                // determine random position of class
                int nr = Configuration.Instance.NumberOfRooms;
                int dur = it.Duration;
                int day = rand() % DAYS_NUM;
                int room = rand() % nr;
                int time = rand() % (DAY_HOURS + 1 - dur);
                int pos = day * nr * DAY_HOURS + room * DAY_HOURS + time;

                // fill time-space slots, for each hour of class
                for (int i = dur - 1; i >= 0; i--)
                    newChromosome._slots[pos + i].Add(it);

                // insert in class table of chromosome
                newChromosome._classes.Add(it, pos);
            }

            newChromosome.CalculateFitness();

            // return smart pointer
            return newChromosome;
        }

        private int rand()
        {
            return Configuration.Instance.Rand.Next();
        }

        public void Crossover(ref Scheduler p1, ref Scheduler p2)
        {
            if (rand() % 100 > _crossoverProbability)
                // no crossover, just copy first parent
                return;

            Scheduler p_temp = p1.Crossover(p2);
            Scheduler p_temp2 = p2.Crossover(p1);

            p1 = p_temp;
            p2 = p_temp2;
        }

        public override bool Equals(object obj)
        {
            var o = obj as Scheduler;
            if (o == null) return false;

            if (o._classes.Count == this._classes.Count &&
                o._fitness == this._fitness &&
                o._slots.Equals(this._slots))
                return true;

            return false;
        }

        public Scheduler Crossover(Scheduler parent2)
        {
            // check probability of crossover operation
            
            //if (rand() % 100 > _crossoverProbability)
            if (rand() % 100 > _crossoverProbability)
                // no crossover, just copy first parent
                return new Scheduler(this, false);

            // new chromosome object, copy chromosome setup
            Scheduler n = new Scheduler(this, true);

            // number of classes
            int size = _classes.Count;

            bool[] cp = new bool[size];

            // determine crossover point (randomly)
            for (int i = _numberOfCrossoverPoints; i > 0; i--)
            {
                while (true)
                {
                    //int p = rand() % size;
                    int p = rand() % size;
                    if (!cp[p])
                    {
                        cp[p] = true;
                        break;
                    }
                }
            }

            //Dictionary<Class, int>::const_iterator it1 = _classes.begin();
            //Dictionary<Class, int>::const_iterator it2 = parent2._classes.begin();
            var it1 = _classes.GetEnumerator();
            var it2 = parent2._classes.GetEnumerator();

            // make new code by combining parent codes
            //bool first = rand() % 2 == 0;
            bool first = rand() % 2 == 0;
            for (int i = 0; /*i < size*/ it1.MoveNext() && it2.MoveNext(); i++)
            {

                if (first)
                {
                    // insert class from first parent into new chromosome's calss table
                    n._classes.Add(it1.Current.Key, it1.Current.Value);
                    // all time-space slots of class are copied
                    for (int j = it1.Current.Key.Duration - 1; j >= 0; j--)
                        n._slots[it1.Current.Value + j].Add(it1.Current.Key);
                }
                else
                {
                    // insert class from second parent into new chromosome's calss table
                    n._classes.Add(it2.Current.Key, it2.Current.Value);
                    // all time-space slots of class are copied
                    for (int j = it2.Current.Key.Duration - 1; j >= 0; j--)
                        n._slots[it2.Current.Value + j].Add(it2.Current.Key);
                }

                // crossover point
                if (cp[i])
                    // change soruce chromosome
                    first = !first;

                //it1.MoveNext();
                //it2.MoveNext();
            }

            n.CalculateFitness();

            // return smart pointer to offspring
            return n;
        }


        public void Mutation()
        {
            //Random rnd = new Random();
            // check probability of mutation operation
            if (rand() % 100 > _mutationProbability)
                return;

            // number of classes
            int numberOfClasses = _classes.Count;
            // number of time-space slots
            int size = _slots.Length;

            // move selected number of classes at random position
            for (int i = _mutationSize; i > 0; i--)
            {
                // select ranom chromosome for movement
                int mpos = rand() % numberOfClasses;
                int pos1 = 0;
                
                //hash_map<Class, int>::iterator it = _classes.begin();
                //var it = _classes.GetEnumerator();
                //var it = _classes.Last();
                //var it = _classes.ElementAt(_classes.Count - rand(_classes.Count) - 1);
                var it = _classes.ElementAt(mpos);
                
                // current time-space slot used by class
                //pos1 = it.Current.Value;
                pos1 = it.Value;

                //Class cc1 = it.Current.Key;
                Class cc1 = it.Key;

                // determine position of class randomly
                int nr = Configuration.Instance.NumberOfRooms;
                int dur = cc1.Duration;
                int day = rand() % DAYS_NUM;
                int room = rand() % nr;
                int time = rand() % (DAY_HOURS + 1 - dur);
                int pos2 = day * nr * DAY_HOURS + room * DAY_HOURS + time;
               
                // move all time-space slots
                for (int k = dur - 1; k >= 0; k--)
                {
                    // remove class hour from current time-space slot
                    List<Class> cl = _slots[pos1 + k];
                    //for( list<Class*>::iterator it = cl.begin(); it != cl.end(); it++ )
                    for (int j = 0; j < cl.Count; j++)
                    {
                        if (cl[j].Equals(cc1))
                        {
                            //cl.RemoveAt(j);
                            _slots[pos1 + k].RemoveAt(j);
                            break;
                        }
                    }

                    // move class hour to new time-space slot
                    _slots[pos2 + k].Add(cc1);
                }

                // change entry of class table to point to new time-space slots
                _classes[cc1] = pos2;
            }

            CalculateFitness();
        }
    
        private void CalculateFitness()
        {
            // chromosome's score
            int score = 0;

            int numberOfRooms = Configuration.Instance.NumberOfRooms;
            int daySize = DAY_HOURS * numberOfRooms;

            int ci = 0;

            // check criterias and calculate scores for each class in schedule
            var it = _classes.GetEnumerator();

            Dictionary<int, Dictionary<Group, int>> studentsDaySch = new Dictionary<int, Dictionary<Group, int>>();

            //for( hash_map<Class*, int>::const_iterator it = _classes.begin(); it != _classes.end(); ++it, ci += 5 )
            for (int j = 0; /*j < _classes.Count;*/ it.MoveNext(); ci += 5)
            {
                // coordinate of time-space slot
                int p = it.Current.Value;
                int day = p / daySize;
                int time = p % daySize;
                int room = time / DAY_HOURS;
                time = time % DAY_HOURS;

                int dur = it.Current.Key.Duration;

                // check for room overlapping of classes
                bool ro = false;
                for (int i = dur - 1; i >= 0; i--)
                {
                    if (_slots[p + i].Count > 1)
                    {
                        ro = true;
                        break;
                    }
                }
            
                // on room overlaping
                if (!ro)
                    score++;
 
                _criteria[ci + 0] = !ro;

                Class cc = it.Current.Key;
                //Room r = Configuration.Instance.GetRoomById(room);
                Room r = Configuration.Instance.Rooms.First(rm => rm.Room_ID == (room + 1));
                
                // does current room have enough seats
                var seats = r.Size >= cc.NumberOfSeats();
                _criteria[ci + 1] = seats;
                if (seats)
                   score++;
           
                // does current room have computers if they are required
                _criteria[ci + 2] = cc.Lab == false || (cc.Lab == true && r.IsLab == true);
                if (_criteria[ci + 2])
                    score++;

                bool po = false, go = false;
                // check overlapping of classes for professors and student groups
                for (int i = numberOfRooms, t = day * daySize + time; i > 0; i--, t += DAY_HOURS)
                {
                    // for each hour of class
                    for (int k = dur - 1; k >= 0; k--)
                    {
                        // check for overlapping with other classes at same time
                        List<Class> cl = _slots[t + k];
                        var iter = cl.GetEnumerator();

                        //for(List<Class>::const_iterator it = cl.begin(); it != cl.end(); it++ )
                        while (iter.MoveNext())
                        {
                            if (!cc.Equals(iter.Current))
                            {
                                // professor overlaps?
                                if (!po && cc.ProfessorOverlaps(iter.Current))
                                    po = true;
                                
                                // student group overlaps?
                                
                                if (!go && cc.GroupsOverlap(iter.Current))
                                    go = true;

                                // both type of overlapping? no need to check more
                                if (po && go)
                                    break;
                            }
                        }
                        if (po && go)
                            break;
                    }
                    if (po && go)
                        break;
                }

                
                //total_overlap:

                // professors have no overlaping classes?
                if (!po)
                    score++;

                _criteria[ci + 3] = !po;

                // student groups has no overlaping classes?
                if (!go)
                    score++;
              

                _criteria[ci + 4] = !go;
            }
           // calculate fitess value based on score
            _fitness = (float)score / (Configuration.Instance.NumberOfClasses * 5);
          
        }

        public double GetFitness()
        {
            return _fitness;
        }

        public List<Class>[] Slots
        {
            get { return _slots; }
        }
    }
}
